/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* $Id: tcpserver_select.c 16 2007-06-10 02:10:04Z kf701.ye $ */

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "kf701.h"

#define MAX_CLIENT	1000

/**
 * @brief TCP server use select
 *
 * Open a listen socket,accept and read data,
 * then deleve data to FUNC, the user defined 
 * FUNC deal with data.
 * 
 * Note: dup buf before create thread in FUNC
 *
 * @param port listen port
 * @param psize the TCP server will read MAX 
 * size data from a new accepted socket
 * @param func user defined FUNC for deal with data
 *
 * @return void
 */
void tcp_server_select( uint16_t port, uint32_t psize, tcp_data_func func )
{
	if( psize <= 0 || NULL == func )
	{
		sys_message("%s,%d: argu err\n", __FILE__, __LINE__);
		return;
	}

	int nread, select_ret;
	int i, max, client;
	int fd[ MAX_CLIENT ];

	int listen_fd = open_listenfd( port );
	if( listen_fd == -1 )
	{
		sys_message("%s,%d: open listenfd err\n",__FILE__,__LINE__);
		return;
	}

	/* init fd array */	
	for( i=0; i<MAX_CLIENT; i++ )
		fd[i] = -1;

	struct sockaddr_in addr;
	socklen_t sock_len = sizeof( struct sockaddr_in );

	uint8_t buf[ psize ];
	fd_set readset;
	max = listen_fd;
	struct timeval tv;
	while( 1 )
	{
		FD_ZERO( &readset );
		FD_SET( listen_fd , &readset );
		for( i=0; i<MAX_CLIENT; i++ )
		{
			if( fd[i] == -1 )
				continue;
			FD_SET( fd[i], &readset );
			if( fd[i] > max )
				max = fd[i];
		}
	
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		select_ret = select( max+1 , &readset , NULL , NULL , &tv );
		if( select_ret < 0 ) 
		{
			sys_message("%s,%d: select err,%m\n", __func__, __LINE__);
			continue;
		} 
		/* time out */
		if( 0 == select_ret )
			continue;

		if ( FD_ISSET( listen_fd , &readset) )
		{
			client = accept( listen_fd, (struct sockaddr*)&addr, &sock_len );
			if( client < 0 )
			{
				sys_message("%s,%d: accept err,%m", __FILE__, __LINE__);
				continue;
			}
			sys_log("%s,%d: connect from %s\n",__FILE__,__LINE__,inet_ntoa(addr.sin_addr));
			setnonblocking( client );
			
			/* add to fd array */
			for( i=0; i<MAX_CLIENT; i++ )
			{
				if( fd[i] == -1 )
				{
					fd[i] = client;
					break;
				}
			}
		}

		for( i = 0 ; i <MAX_CLIENT ; i++ )
		{
			if( fd[i] != -1 && FD_ISSET( fd[i] , &readset ) )
			{
				nread = rio_read( fd[i], buf, sizeof(buf) );
				if ( nread <= 0 )
				{
					close( fd[i] );
					fd[i] = -1;
					sys_message("%s,%d: read err,close socket!\n", __func__, __LINE__);
					continue;
				}
				/* Note: dup buf before create thread in func */
				func( fd[i], buf, nread );
			}
		}
	}
}

/**
 * @brief TCP server use select
 *
 * Note: different with tcp_server_select(),the routine
 * never select accepted socket fd or read data from them
 *
 * Every new accepted socket used in FUNC
 * and will be closed in FUNC and subfunc
 * Best idea is creating a thread in FUNC
 *
 * @param port listen port
 * @param func deal with socket fd
 *
 */
void tcp_server_select_2( uint16_t port, tcp_fd_func func )
{
	if( NULL == func )
	{
		sys_message("%s,%d: argu err\n", __FILE__, __LINE__);
		return;
	}

	int listen_fd = open_listenfd( port );
	if( listen_fd == -1 )
	{
		sys_message("%s,%d: open listenfd err\n",__FILE__,__LINE__);
		return;
	}

	struct sockaddr_in addr;
	socklen_t sock_len = sizeof( struct sockaddr_in );

	int select_ret, client;
	fd_set readset;
	struct timeval tv;
	while( 1 )
	{
		FD_ZERO( &readset );
		FD_SET( listen_fd , &readset );
	
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		select_ret = select( listen_fd+1 , &readset , NULL , NULL , &tv );
		if( select_ret < 0 ) 
		{
			sys_message("%s,%d: select err,%m\n", __func__, __LINE__);
			continue;
		} 
		/* time out */
		if( 0 == select_ret )
			continue;

		if ( FD_ISSET( listen_fd , &readset) )
		{
			client = accept( listen_fd, (struct sockaddr*)&addr, &sock_len );
			if( client < 0 )
			{
				sys_message("%s,%d: accept err,%m", __FILE__, __LINE__);
				continue;
			}
			sys_log("%s,%d: connect from %s\n",__FILE__,__LINE__,inet_ntoa(addr.sin_addr));
			func( client );
		}
	}
}
